package com.billdowney.webconfig;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.vote.AuthenticatedVoter;
import org.springframework.security.access.vote.RoleVoter;
import org.springframework.security.access.vote.UnanimousBased;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.expression.WebExpressionVoter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.session.HttpSessionEventPublisher;
import org.springframework.security.web.session.SessionInformationExpiredStrategy;
import org.springframework.web.cors.CorsUtils;

import com.billdowney.entity.auth.AuthAction;
import com.billdowney.security.SecurityConst;
import com.billdowney.security.filter.UserAuthenticationFilter;
import com.billdowney.security.handler.CustomAccessDeniedHandler;
import com.billdowney.security.handler.LoginFailHandler;
import com.billdowney.security.handler.LoginLogoutHandler;
import com.billdowney.security.handler.LoginSuccessHandler;
import com.billdowney.security.voter.UrlAccessVoter;
import com.billdowney.service.impl.security.AuthenticationEntryPointServiceImpl;
import com.billdowney.service.impl.security.UserDetailsServiceImpl;
import com.billdowney.service.interfaces.auth.AuthManageService;
import com.billdowney.service.util.project.ProjectModelUtil;
import com.billdowney.util.CommonUtil;
import com.billdowney.util.auth.AuthRoleConst;

@Configuration
@EnableWebSecurity
//启用方法级别的权限认证
//@EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	// 项目模式工具
	@Autowired
	private ProjectModelUtil projectModelUtil;
	// 登录处理异常
	@Autowired
	private AuthenticationEntryPointServiceImpl authenticationEntryPointServiceImpl;
	// 登录成功处理
	@Autowired
	private LoginSuccessHandler loginSuccessHandler;
	// 登录失败处理
	@Autowired
	private LoginFailHandler loginFailHandler;
	// 登出成功处理
	@Autowired
	private LoginLogoutHandler loginLogoutHandler;
	// 自定义用户详情获取实现类
	@Autowired
	private UserDetailsServiceImpl userDetailsServiceImpl;
	// 自定义投票器
	@Autowired
	private UrlAccessVoter urlAccessVoter;
	// 自定义权限不足处理程序
	@Autowired
	private CustomAccessDeniedHandler customAccessDeniedHandler;
	// 授权管理服务器类
	@Autowired
	private AuthManageService authManageService;
	// session管理
	@Autowired
	private SessionRegistry sessionRegistry;
	// 定义一个Strategy，在登录成功时注册一个用户进sessionRegistry
	@Autowired
	private SessionAuthenticationStrategy sessionAuthenticationStrategy;
	// session过期处理
	@Autowired
	private SessionInformationExpiredStrategy expiredSessionStrategy;
	@Autowired
	private SecurityProperties securityProperties;

	/**
	 * 注入security加密工具
	 * 
	 * @return
	 * @author: 超级小富翁 - BillDowney
	 * @date: 2019年2月10日 下午10:17:44
	 */
	@Bean
	public PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}

	@Bean
	public DaoAuthenticationProvider authenticationProvider() {
		DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
		provider.setHideUserNotFoundExceptions(false);
		provider.setUserDetailsService(userDetailsServiceImpl);
		provider.setPasswordEncoder(passwordEncoder());
		return provider;
	}

	@Bean
	public SessionRegistry getSessionRegistry() {
		return new SessionRegistryImpl();
	}

	@Bean
	public SessionAuthenticationStrategy getSessionAuthenticationStrategy() {
		return new RegisterSessionAuthenticationStrategy(sessionRegistry);
	}

	/** 定义认证用户信息获取来源，密码校验规则等 */
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		// inMemoryAuthentication 从内存中获取
		// auth.inMemoryAuthentication().withUser("admin").password(passwordEncoder().encode("123456")).roles("USER","ADMIN");
		// jdbcAuthentication从数据库中获取，但是默认是以security提供的表结构
		// usersByUsernameQuery 指定查询用户SQL
		// authoritiesByUsernameQuery 指定查询权限SQL
		// auth.jdbcAuthentication().dataSource(dataSource).usersByUsernameQuery(query).authoritiesByUsernameQuery(query);

		// 注入userDetailsService，需要实现userDetailsService接口
		// auth.userDetailsService(userDetailsServiceImpl);
		// 添加自定义的provider
		auth.authenticationProvider(authenticationProvider());
		// 开发模式下添加临时登陆账户
		if (projectModelUtil.isDeveloperMode()) {
			auth.inMemoryAuthentication().withUser(securityProperties.getUser().getName())
					.password(passwordEncoder().encode(securityProperties.getUser().getPassword()))
					.roles(CommonUtil.listToArray(securityProperties.getUser().getRoles()));
		}
	}

	/** 定义安全策略 */
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		// 解决不允许显示在iframe的问题
		http.headers().frameOptions().disable();
		http.csrf().disable();
		http.cors();
		http.authorizeRequests()// 配置安全策略
				.accessDecisionManager(accessDecisionManager())// 自定义accessDecisionManager
				.requestMatchers(CorsUtils::isPreFlightRequest).permitAll();// 处理跨域请求中的Preflight请求
		// session管理
		// session失效后跳转
		// http.sessionManagement().invalidSessionUrl("/login");
		// 只允许一个用户登录,如果同一个账户两次登录,那么第一个账户将被踢下线,跳转到登录页面(.expiredUrl("/login"))
		http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
				.maximumSessions(SecurityConst.MAXIMUM_SESSIONS).expiredSessionStrategy(expiredSessionStrategy)
				.sessionRegistry(sessionRegistry);
		if (projectModelUtil.isDeveloperMode()) {
			http.authorizeRequests()// 配置安全策略
					.anyRequest().permitAll();// 需要授权
		} else {
			this.authUrlAndRole(http);
		}
		// 配置匿名访问角色
		// http.anonymous().authorities(AuthRoleConst.ROLE_SUPERADMIN);
		http.logout().permitAll().logoutUrl(SecurityConst.LOGOUT_URL)// 定义logout不需要验证
				.logoutSuccessHandler(loginLogoutHandler)// 登出成功处理
				.invalidateHttpSession(true);// 退出请求后销毁session
		// 注册exception处理器，替换原登录页
		http.exceptionHandling().authenticationEntryPoint(authenticationEntryPointServiceImpl)
				// 自定义权限不足异常处理
				.accessDeniedHandler(customAccessDeniedHandler);
		// 用重写的Filter替换掉原有的UsernamePasswordAuthenticationFilter，需要自己注入某些设置，例如successHandler
		http.addFilterAt(userAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
		// 使用form表单登录，支持JSON登录
		http.formLogin().loginProcessingUrl(SecurityConst.LOGIN_URL).successHandler(loginSuccessHandler)
				.failureHandler(loginFailHandler).usernameParameter(SecurityConst.USERNAME_FIELD)
				.passwordParameter(SecurityConst.PASSWORD_FIELD).permitAll();
	}

	/**
	 * 授权url和角色的关联
	 * 
	 * @author: 超级小富翁 - BillDowney
	 * @throws Exception
	 * @date: 2019年2月27日 下午6:23:48
	 */
	public void authUrlAndRole(HttpSecurity http) throws Exception {
		Map<String, List<AuthAction>> map = authManageService.classifyRoleAuthAction();
		for (Entry<String, List<AuthAction>> entry : map.entrySet()) {
			List<String> list = new ArrayList<String>();
			for (AuthAction action : entry.getValue()) {
				list.add(action.getFilterUrl());
			}
			switch (entry.getKey()) {
			case AuthRoleConst.NOT_CONFIG:// 配置没有配置注解的接口
				http.authorizeRequests().antMatchers(CommonUtil.listToArray(list)).anonymous();
				break;
			case AuthRoleConst.NOT_LOGIN:// 配置不需要登录访问的接口
				http.authorizeRequests().antMatchers(CommonUtil.listToArray(list)).anonymous();
				break;
			case AuthRoleConst.ROLE_LOGIN:// 配置登录访问的接口
				http.authorizeRequests().antMatchers(CommonUtil.listToArray(list)).authenticated();
				break;
			default:// 其它接口需要登录访问
				if (entry.getKey().startsWith(AuthRoleConst.ROLE_PREFIX)) {
					http.authorizeRequests().antMatchers(CommonUtil.listToArray(list))
							.hasRole(entry.getKey().replaceFirst(AuthRoleConst.ROLE_PREFIX, ""));
				} else {
					http.authorizeRequests().antMatchers(CommonUtil.listToArray(list)).hasRole(entry.getKey());
				}
				break;
			}
		}
	}

	/**
	 * 注册自定义的UsernamePasswordAuthenticationFilter
	 * 
	 * @return
	 * @throws Exception
	 * @author: 超级小富翁 - BillDowney
	 * @date: 2019年2月7日 上午10:35:59
	 */
	@Bean
	public UserAuthenticationFilter userAuthenticationFilter() throws Exception {
		UserAuthenticationFilter filter = new UserAuthenticationFilter();
		// 这句很关键，重用WebSecurityConfigurerAdapter配置的AuthenticationManager，不然要自己组装AuthenticationManager
		filter.setAuthenticationManager(authenticationManagerBean());
		filter.setAuthenticationSuccessHandler(loginSuccessHandler);
		filter.setAuthenticationFailureHandler(loginFailHandler);
		filter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
		return filter;
	}

	/**
	 * 注入自定义accessDecisionManager
	 * 
	 * @return
	 * @author: 超级小富翁 - BillDowney
	 * @date: 2019年2月11日 下午5:28:56
	 */
	@Bean
	public AccessDecisionManager accessDecisionManager() {
		List<AccessDecisionVoter<? extends Object>> decisionVoters = Arrays.asList(new WebExpressionVoter(),
				new RoleVoter(), new AuthenticatedVoter(), urlAccessVoter);
		return new UnanimousBased(decisionVoters);
	}

	/**
	 * spring security监听器
	 * 
	 * @return
	 * @author: 超级小富翁 - BillDowney
	 * @date: 2019年3月13日 下午6:43:28
	 */
	@Bean
	public HttpSessionEventPublisher HttpSessionEventPublisher() {
		return new HttpSessionEventPublisher();
	}
	/*
	 * @Bean public ServletListenerRegistrationBean<EventListener> listenerRegist()
	 * { ServletListenerRegistrationBean<EventListener> srb = new
	 * ServletListenerRegistrationBean<EventListener>();
	 * srb.setListener(HttpSessionEventPublisher()); return srb; }
	 */

}
